package cn.seaboot.admin.api.mapping;

import cn.seaboot.admin.api.bean.ApiAction;
import cn.seaboot.admin.api.bean.ApiMapping;
import cn.seaboot.commons.core.Snowflake;
import cn.seaboot.commons.core.SnowflakeFactory;
import cn.seaboot.commons.reflect.ObjectUtils;
import cn.seaboot.openapi.base.ControllerInfo;
import cn.seaboot.openapi.base.MethodInfo;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 扫描一个方法上的映射配置
 *
 * @author Mr.css
 * @version 2022-04-18 11:41
 */
@Component
public class MappingScanner {
    private final Logger logger = LoggerFactory.getLogger(MappingScanner.class);

    private final Snowflake snowflake = SnowflakeFactory.createSnowflake();

    /**
     * 获取一个方法上的所有映射配置
     *
     * @param controllerInfo controller信息扫描
     * @param methodInfo     方法信息扫描
     * @param apiAction       工程ID
     */
    public List<ApiMapping> scanApiMapping(ApiAction apiAction, ControllerInfo controllerInfo, MethodInfo methodInfo) {
        List<ApiMapping> ret = new ArrayList<>();
        for (String u1 : controllerInfo.getRequestPath()) {
            for (String u2 : methodInfo.getRequestPath()) {
                String servletPath = u1 + u2;

                // don't repeat scan, if exist api-mapping, continue next loop.
                if (isIgnoreMappingUri(servletPath)) {
                    continue;
                }


                ApiMapping mapping = new ApiMapping();

                // generate guid
                mapping.setId(this.snowflake.nextId());

                // info
                mapping.setProjectId(apiAction.getProjectId());
                mapping.setControllerClass(controllerInfo.getTarget().getName());
                mapping.setDescription(methodInfo.getMethodDescription());

                // request
                mapping.setRequestMapping(servletPath);
                mapping.setRequestMethod(methodInfo.getRequestMethod());
                mapping.setFormType(methodInfo.getFormType().name());
                mapping.setMethodName(methodInfo.getMethod().getName());

                // response
                scanReturnType(mapping, methodInfo);
                ret.add(mapping);
            }
        }
        return ret;
    }


    /**
     * 扫描方法的返回值，此处有重复扫描代码的嫌疑，
     * 一个函数支持配置多个 RequestMapping，这种情况下会多次执行，
     * 但是实际开发中一般用不到这种配置方式，不必尝试优化本次的代码。
     *
     * @param mapping    映射
     * @param methodInfo 方法信息扫描
     */
    private void scanReturnType(ApiMapping mapping, MethodInfo methodInfo) {
        Class<?> type = methodInfo.getMethod().getReturnType();
        mapping.setResponseClass(type.getName());

        if (PageInfo.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type)) {
            // if return-value is collection make first generic as type, and scan sub model
            Type generic = ObjectUtils.getFirstReturnGeneric(methodInfo.getMethod());
            if (generic != null) {
                if (generic instanceof ParameterizedType) {
                    logger.info("parameterized type: {}", generic);
                }
                mapping.setResponseGeneric(generic.getTypeName());
            } else {
                logger.info("unknown generics: {}", type);
            }
        } else if (type.isArray()) {
            // if return-value is array make component-type as type
            type = type.getComponentType();
            mapping.setResponseGeneric(type.getName());
        }
    }

    /**
     * 暂不支持 restful 的写法
     *
     * @param uri 路径
     * @return if exist or not
     */
    private boolean isIgnoreMappingUri(String uri) {
        return uri.indexOf('{') >= 0;
    }
}
